﻿using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.EventArgs;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace RaceHFTools.BluetoothLE.Bean.Module
{
    public enum INFO_COMMAND
    {
        ALL,
        BATTERY,
        USB,
        SDCARD,
    };

    public delegate void InfoGetBatteryEventHandler(uint battery);
    public delegate void InfoGetUsbEventHandler(bool usb);
    public delegate void InfoGetSdcardEventHandler(bool sdcard);


    public class Info
    {
        public struct Gnss
        {
            public double Longitude;       /* 经度 */
            public double Latitude;        /* 纬度 */
            public int Height;             /* 海拔 */
            public uint Locatemode;         /* 定位方式 */
            public uint Seconds;           /* UNIX时间戳 */
            public uint MilliSeconds;      /* 毫秒数 */
            public float Speed;            /* 速度 */
            public float Direction;        /* 方位角 */
            public float HDOP;             /* HDOP */
            public uint Satellites;        /* 锁定卫星数 */
            public uint VisableSats;       /* 可视卫星数 */
            public uint GpsSats;           /* GPS卫星数 */
            public uint GlonassSats;       /* GLONASS卫星数 */
            public uint GalileoSats;       /* GALILEO卫星数 */
            public uint BeidouSats;        /* BEIDOU卫星数 */
        }

        public struct Gsensor
        {
            public float X;
            public float Y;
            public float Z;
            public float SUM;
        }

        public delegate void GnssEvent(Gnss gnss);
        public delegate void GsensorEvent(Gsensor gsensor);
        public delegate void DebugEvent(byte[] data);

        public GnssEvent OnGnssChanged;
        public GsensorEvent OnGsensorChanged;
        public DebugEvent OnDebugChanged;

        public InfoGetBatteryEventHandler InfoGetBatteryEvent;
        public InfoGetUsbEventHandler InfoGetUsbEvent;
        public InfoGetSdcardEventHandler InfoGetSdcardEvent;

        public readonly Guid INFO_DATA_UUID = new Guid("0000aaa1-0000-1000-8000-00805f9b34fb");
        public readonly Guid INFO_STATE_UUID = new Guid("0000aaa2-0000-1000-8000-00805f9b34fb");

        readonly Bean bean;
        readonly ICharacteristic info_data_char, info_state_char;

        Gnss gnss;
        Gsensor gsensor;

        public Info(Bean bean)
        {
            this.bean = bean;

            info_data_char = this.bean.Transport.FindCharacteristicById(INFO_DATA_UUID);
            info_state_char = this.bean.Transport.FindCharacteristicById(INFO_STATE_UUID);
            if (info_data_char == null || info_state_char == null)
                throw new Exception("No valid characteristic");
            info_state_char.WriteType = CharacteristicWriteType.WithoutResponse;

        }

        void NotifyHandler(object sender, CharacteristicUpdatedEventArgs e)
        {
            ICharacteristic characteristic = e.Characteristic;
            byte[] val = characteristic.Value;

            if (characteristic == info_data_char)
                DataNotifyHandler(val);
            else if (characteristic == info_state_char)
                StateNotifHandler(val);
        }

        void StateNotifHandler(byte[] data)
        {
            if (data.Length < 2)
                return;

            INFO_COMMAND cmd = (INFO_COMMAND)data[0];
            int length = data.Length - 1;
            data = data.Skip(1).ToArray();

            switch(cmd)
            {
                case INFO_COMMAND.BATTERY:
                    if (length == 1)
                    {
                        uint battery = data[0];
                        InfoGetBatteryEvent?.Invoke(battery);
                    }
                    break;

                case INFO_COMMAND.USB:
                    if (length == 1)
                    {
                        bool usb = data[0] != 0;
                        InfoGetUsbEvent?.Invoke(usb);
                    }
                    break;

                case INFO_COMMAND.SDCARD:
                    if (length == 1)
                    {
                        bool sdcard = data[0] != 0;
                        InfoGetSdcardEvent?.Invoke(sdcard);
                    }
                    break;

                default:
                    break;
            }
        }

        void DataNotifyHandler(byte[] data)
        {
            if (data[0] == 0x10 && data.Length == 20)
            {
                double longitude = BitConverter.ToDouble(data, 1);
                double latitude = BitConverter.ToDouble(data, 9);
                short height = BitConverter.ToInt16(data, 17);
                byte locatemode = data[19];

                gnss.Longitude = longitude;
                gnss.Latitude = latitude;
                gnss.Height = height;
                gnss.Locatemode = locatemode;
            }
            else if (data[0] == 0x11 && data.Length == 20)
            {
                uint seconds = BitConverter.ToUInt32(data, 1);
                ushort milliseconds = BitConverter.ToUInt16(data, 5);
                float speed = BitConverter.ToSingle(data, 7);
                float direction = BitConverter.ToSingle(data, 11);
                float hdop = BitConverter.ToSingle(data, 15);
                byte satellites = data[19];

                gnss.Seconds = seconds;
                gnss.MilliSeconds = milliseconds;
                gnss.Speed = speed;
                gnss.Direction = direction;
                gnss.HDOP = hdop;
                gnss.Satellites = satellites;
            }
            else if (data[0] == 0x12 && data.Length == 6)
            {
                uint vis = data[1];
                uint gps = data[2];
                uint glonass = data[3];
                uint galileo = data[4];
                uint beidou = data[5];

                gnss.VisableSats = vis;
                gnss.GpsSats = gps;
                gnss.GlonassSats = glonass;
                gnss.GalileoSats = galileo;
                gnss.BeidouSats = beidou;

                OnGnssChanged?.Invoke(gnss);
            }
            else if (data[0] == 0x21 && data.Length == 17)
            {
                float x = BitConverter.ToSingle(data, 1);
                float y = BitConverter.ToSingle(data, 5);
                float z = BitConverter.ToSingle(data, 9);
                float a = BitConverter.ToSingle(data, 13);

                gsensor.X = x;
                gsensor.Y = y;
                gsensor.Z = z;
                gsensor.SUM = a;

                OnGsensorChanged?.Invoke(gsensor);
            }
            else if (data[0] == 0xff && data.Length > 1)
            {
                byte[] ndata = new byte[data.Length - 1];
                Array.Copy(data, 1, ndata, 0, ndata.Length);
                OnDebugChanged?.Invoke(ndata);
            }
        }

        public async Task StartListening()
        {
            await bean.Transport.EnableNotify(info_data_char);
            await bean.Transport.EnableNotify(info_state_char);
            info_data_char.ValueUpdated += NotifyHandler;
            info_state_char.ValueUpdated += NotifyHandler;
        }

        public async Task StopListening()
        {
            await bean.Transport.DisableNotify(info_data_char);
            await bean.Transport.DisableNotify(info_state_char);
            info_data_char.ValueUpdated -= NotifyHandler;
            info_state_char.ValueUpdated -= NotifyHandler;
        }

        async Task SendCommand(byte cmd, byte[] data = null)
        {
            byte[] command = new byte[] { cmd };
            if (data != null)
                command = command.Concat(data).ToArray();
            await bean.Transport.Write(info_state_char, command);
        }

        public async Task GetInfoAll()
        {
            await SendCommand((byte)INFO_COMMAND.ALL);
        }

        public async Task GetInfoBattery()
        {
            await SendCommand((byte)INFO_COMMAND.BATTERY);
        }

        public async Task GetInfoUsb()
        {
            await SendCommand((byte)INFO_COMMAND.USB);
        }

        public async Task GetInfoSdcard()
        {
            await SendCommand((byte)INFO_COMMAND.SDCARD);
        }
    }
}
